package com.seleniti.enpareja.web.desktop.client.ui.widget;

import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import org.ioc.commons.impl.gwt.shared.common.util.ObservableHashSet;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.RowCountChangeEvent;
import com.google.gwt.view.client.RowCountChangeEvent.Handler;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.HasSelectionChangedHandlers;

public class Checker<T> extends Composite implements HasSelectionChangedHandlers {

	public enum SelectionMode {
		SingleSelection, Multiselection
	}

	private final VerticalPanel panel;
	private final int checkHeight;
	private SelectionMode selectionMode;
	private HasData<T> display;
	private String name;
	private int checksPerRow;
	private int rowOffset;
	private final ObservableHashSet<T> selectedItems = new ObservableHashSet<>();
	private boolean requiresRefresh = true;

	public Checker(int checkHeight) {
		this(checkHeight, 1);
	}

	public Checker(int checkHeight, int checksPerRow) {
		this.checkHeight = checkHeight / checksPerRow;
		this.checksPerRow = checksPerRow;
		this.panel = new VerticalPanel();
		this.panel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		this.panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		this.name = DOM.createUniqueId();

		selectedItems.addObserver(new Observer() {

			@Override
			public void update(Observable o, Object arg) {
				if (requiresRefresh) {
					requiresRefresh = false;
					Scheduler.get().scheduleFinally(new ScheduledCommand() {

						@Override
						public void execute() {
							refreshChecks();
							requiresRefresh = true;
						}

					});
				}

			}
		});

		initWidget(panel);
	}

	public void addDataDisplay(final HasData<T> display) {
		this.display = display;
		display.addRowCountChangeHandler(new Handler() {

			@Override
			public void onRowCountChange(RowCountChangeEvent event) {
				int count = event.getNewRowCount();

				refresh(count * checksPerRow);
			}
		});
		refresh(display.getRowCount() * checksPerRow);
	}

	public void refresh(int count) {
		if (panel.getWidgetCount() < count) {
			for (int i = panel.getWidgetCount(); i < count; i++) {
				final CheckBox cb = this.selectionMode == SelectionMode.Multiselection ? new CheckBox()
						: new RadioButton(name);

				cb.addValueChangeHandler(new ValueChangeHandler<Boolean>() {

					@Override
					public void onValueChange(ValueChangeEvent<Boolean> event) {
						int idx = panel.getWidgetIndex(cb);
						if (idx < display.getVisibleItemCount()) {
							T vi = display.getVisibleItem(idx);
							if (vi != null) {
								boolean prev = requiresRefresh;
								requiresRefresh = false;
								if (Boolean.TRUE.equals(event.getValue())) {
									selectedItems.add(vi);
								} else {
									selectedItems.remove(vi);
								}

								requiresRefresh = prev;
							}
						}
						SelectionChangeEvent.fire(Checker.this);
					}
				});

				panel.add(cb);
				panel.setCellHeight(cb, (checkHeight - (rowOffset / checksPerRow)) + "px");

				if (rowOffset > 0 && i % checksPerRow == 0) {
					cb.getElement().getParentElement().getStyle().setPaddingTop(rowOffset, Unit.PX);
				}
			}
		} else if (panel.getWidgetCount() > count) {
			for (int i = panel.getWidgetCount() - 1; i >= count; i--) {
				panel.remove(i);
			}
		}
	}

	public void setSelectionMode(SelectionMode selectionMode) {
		this.selectionMode = selectionMode;
		panel.clear();
		refresh(display.getRowCount() * checksPerRow);
	}

	public SelectionMode getSelectionMode() {
		return selectionMode;
	}

	@Override
	public HandlerRegistration addSelectionChangeHandler(com.google.gwt.view.client.SelectionChangeEvent.Handler handler) {
		return this.addHandler(handler, SelectionChangeEvent.getType());
	}

	public Set<T> getSelectedItems() {
		return this.selectedItems;
	}

	public boolean[] getSelection() {
		boolean[] selection = new boolean[panel.getWidgetCount()];
		for (int i = 0; i < panel.getWidgetCount(); i++) {
			CheckBox cb = (CheckBox) panel.getWidget(i);
			selection[i] = Boolean.TRUE.equals(cb.getValue());
		}
		return selection;
	}

	public int getRowOffset() {
		return rowOffset;
	}

	public void setRowOffset(int rowOffset) {
		this.rowOffset = rowOffset;
	}

	private void refreshChecks() {
		for (int i = 0; i < this.display.getVisibleItemCount(); i++) {
			T vi = display.getVisibleItem(i);
			boolean selected = this.selectedItems.contains(vi);
			if (i < panel.getWidgetCount()) {
				CheckBox cb = (CheckBox) panel.getWidget(i);
				cb.setValue(selected, false);
			}
		}

	}
}
